package edu.bu.cs511.p5;

/**
 * edu.bu.cs511.p5.OpcodeTask class.  OpcodeTasks are the
 * representation in the scheduler hierarchy of the opcodes
 * themselves.<p/>
 *
 * All nodes in the scheduler hierarchy (which is a Composite design
 * pattern) that are Leafs (of the composite design pattern) are
 * OpcodeTasks.  They signify the leaf nodes where execution
 * terminates.<p/>
 *
 * OpcodeTasks have an opcode associated with them conforming to the
 * IOpcode interface.  This opcode is the Visitor part of the
 * composite pattern, and the functionality of the opcode is defined
 * in that class.<p/> OpcodeTasks describe the view that schedulers
 * have of opcodes.  <p/>
 *
 * 2/16/06, Gabriel Parmer: initial template<p/>
 *
 * 2/22/06, Gabriel Parmer: termination and concurrency levels
 */
public class OpcodeTask extends Task {
    private boolean inSXE = true;
    private IOpcode opcode = null;

    /* the concurrency level of this opcode */
    private int concurrency = 0;

    /**
     * Constructor to create a opcode task.<p/>
     *
     * @REQUIRES opcode is the opcode conforming to the IOpcode
     * interface which will define the opcode behavior for this task.
     * It follows the Visitor pattern.  parent is this tasks parent
     * scheduler.<p/>
     *
     * @MODIFIES sets the opcode for this task to be opcode and the
     * parent scheduler to parent.  Note that the invariant that all
     * components must be SchedTasks is maintained via typing here.
     */
    public OpcodeTask(IOpcode opcode, SchedTask parentScheduler) {
        super(parentScheduler);
        setOpcode(opcode);
    }

    /**
     * Constructor to create a opcode task.<p/>
     *
     * @REQUIRES opcode is the opcode conforming to the IOpcode
     * interface which will define the opcode behavior for this task.
     * It follows the Visitor pattern.  The parent of this task is
     * defaulted to null.<p/>
     *
     * @MODIFIES sets the opcode for this task to be opcode.
     */

    public void setOpcode(IOpcode opcode) {
        this.opcode = opcode;
    }

    /**
     * Run the opcode, return if it is blocked or not.  If we are in
     * the SXE environment, then tasks will always block if they have
     * not been enabled.  Outside of the SXE environment, for testing
     * or demonstration, tasks should not block to demonstrate and
     * test the most interesting CPU bound behavior.  Note the IOpcode
     * object follows the Command Pattern.<p/>
     *
     * @EFFECTS Return if the task is not ready to execute again or
     * not, ie. if it is "blocked".  In the SXE until a task is
     * enabled, it is blocked.  <p/>
     *
     * @MODIFIES Execute the opcode and update our concurrency level.
     */
    public boolean executeTask() {
        printDebug(getName());

        opcode.execute();

        // FIXME: Need to look at interface in SXE for opcodes to see
        // if there is any way to specify that an opcode is not
        // blocked (might be long-running).  This will probably remain
        // hardcoded, till perhaps long-running, cooperative tasks are
        // supported.
        //return true;
	if (inSXE) {
	    concurrency--;
	    childConcurrencyLevel(this, concurrency);

	    /**
	     * if we still have some input available to us, say that
	     * we are not blocking, otherwise tell the scheduler to
	     * block us. 
	     */
	    if (concurrency < 1) {
		return true;
	    } else {
		return false;
	    }
	} else {
	    return false;
	}
    }

    /** 
     * Terminate this opcode task.<p/>
     *
     * @MODIFIES Terminate this task such that it will not make any
     * more concurrency requests to its parent, and will, in fact, not
     * maintain a parent any longer.
     */
    public void endTask() {
        //SchedTask parent = getParentScheduler();

        //parent.terminateChild(this);

       /*
       * now next time this task is scheduled, the scheduler will
       * remove it
       */
        terminated = true;
        setParentSchedTask(null);
    }


    /**
     * Because we are a leaf of the scheduling hierarchy, this method
     * in only invoked by the enabler (the policy that determines
     * which tasks can run).  It is perferred that one use the
     * enableOpcode interface.<p/>
     *
     * @REQUIRES child can be any value as it is not used, and
     * concurrency must be the value of this task's concurrency.<p/>
     *
     * @MODIFIES Update this task's concurrency level to be
     * concurrency, and call the same method in our parent, updating
     * our concurrency.
     */
    protected void childConcurrencyLevel(Task child, int concurrency) {
        Task parent = getParentScheduler();

        if (parent == null) {
	    /* we have been removed from the hierarchy since we ran last */
            //System.out.println("BUG in edu.bu.cs511.p5.OpcodeTask: opcode without scheduler as parent.\n");
            return;
        }

        parent.childConcurrencyLevel(this, concurrency);
    }

    /**
     * Call to enable an opcode, which will trigger notifications down the
     * scheduling hierarchy.  We assume that opcodes only have one thread, thus
     * our concurrency level is 1.<p/>
     *
     * @MODIFIES Increase the concurrency of this task, and tell our
     * parent that we have this increased concurrency so that it will
     * start scheduling us.
     */
    public void enableOpcode() {
	/* mjo: I dislike the idea of the same opcode needing to run 
	 * more than once.  This change is only necessary if we can't
	 * trust the evaluator thread to wait to re-enable until after
	 * it's been run, *but* ... 
	 * 
	 * the GraphEvaluatorThread iterates over all enabled nodes and
	 * adds them to the queue.  If the iterator hits that node again
	 * (before the node has started running and is marked no longer
	 * enabled) this means the node will be run twice.  Dropping the
	 * max concurrency to 1 prevents this problem.
	 */
	    if(concurrency<1){
	    	concurrency++;
	        childConcurrencyLevel(null, concurrency);
	    }
    }

    /**
     * Get the opcode this task is wrapping<p/>
     *
     * @EFFECTS Return the opcode associated with this task.
     */
    public IOpcode getOpcode() {
        return opcode;
    }

    /**
     * Set if this opcode is to behave like it is in the SXE
     * environment, as it defaults to being in the SXE.  The
     * difference is that in the SXE, opcodes only run once per time
     * they are enabled, whereas outside the SXE, it is useful if they
     * never block, and can be run without enabling them.<p/>
     *
     * @REQUIRES in is a boolean and specifies if the opcode is to
     * behave as if it is in the SXE or not. <p/>
     *
     * @MODIFIES inSXE is set to in.
     */
    public void setInSXE(boolean in) {
	inSXE = in;
    }
}
